#! /usr/bin/python
# -*- coding: utf-8 -*-
# plate trace and invoke the subscribe.

"""
进行物料跟踪，主要涉及到2个计算点和2个采集点，到这4个节点，调用相应的程序进行计算和数据记录
"""


import sys
import os
import time
#import datetime
import threading
import snap7
from collections import deque
import struct

import global_var

from plate_model import *
from plate_syndata import *
from db_ctrl import *
from db_conf import *



log = global_var.gl_log

BASE_TABLE = "flattensys"
PLC_ADDR = "192.100.1.45"

#轧线状态值
LINE_STATUS={
    "cur_plate":"000000000000",#当前带钢ID
    "calc2_on": 0,#到达计算点2fl
    "calc2_cd": 0,#倒计时(count down)
    "L1_seton": 0,#=1, L1下设备有滞后，当有设定出现，需要保存到数据库
}

class PLCDBlk:
    def __init__(self, idx, stx, edx):
        
        self.idx = idx
        self.stx = stx
        self.edx = edx
        self.datablk = None

    def __str__(self):
        return "[{},{},{}]".format(self.idx, self.stx, self.edx)    

class CPlateTrace(threading.Thread):
    q = deque(maxlen=10)
    model = CFlattenModel() # model calculator
    synpd = CPlateDataSyn() # plate data syn
    db = CDBProcessor() #data process: save, select
    db.connect()

    #calc1_plateid = ""#一次计算的带钢号

    def __init__(self):
        threading.Thread.__init__(self)
        self.db_dict = {}
        self.client = snap7.client.Client()
        self.client.connect(PLC_ADDR,0,3)
        self.init_plc_db()
    
    def run(self):
        
        while global_var.gl_RUNNING:
            sleep_time = 1.0
            try:
                self.q.append(self.read_plc_db())
                #print("deque size: ", len(self.q), ", ", len(self.q[0]))
                # print(self.q[-1]["plate_id"],
                #      ",",self.q[-1]["Before In Table1"],
                #      ",",self.q[-1]["After Out Table1"],
                #      ",",self.q[-1]["Before In Table2"],
                #      ",",self.q[-1]["After Out Table2"],
                #      ",",self.q[-1]["Before PinchRoll"],
                #      ",",self.q[-1]["Before Leveller"],
                #      '----',self.q[-1]["PinchRoll Occupied"],
                #      ',',self.q[-1]["Leveller Occupied"],
                #      ',',self.q[-1]["Forward mode"],
                #      ',',self.q[-1]["Reverse mode"],
                #      ',',self.q[-1]["Forward running"],
                #      ',',self.q[-1]["Reverse running"],
                #      '----',self.q[-1]["Leverler inlet manual"],
                #      ',',self.q[-1]["Leverler outlet manual"],
                #      ',',self.q[-1]["Leverler flexion manual"]
                #            )
                #处理跟踪数据：找到计算点，采集点
                self.proc_trace()
            except Exception as e:
                print("error: ",e)

            time.sleep(sleep_time)    

    #proccess trace data and invoke process function
    def proc_trace(self):

        
        list_result_name = [
            "Leverler inlet manual",
            "Leverler outlet manual",
            "Leverler flexion manual",
            "flmg_mean",#full length maxgap预测期望值
            "fcmg_mean",#full cross maxgap预测期望值
            "flmg_prct_90",#full length maxgap 90%的预测最大范围
            "fcmg_prct_90" #full cross maxgap 90%的预测最大范围
        ]

        list_pre_result = [
            "flmg_mean",#full length maxgap预测期望值
            "fcmg_mean",#full cross maxgap预测期望值
            "flmg_prct_90",#full length maxgap 90%的预测最大范围
            "fcmg_prct_90" #full cross maxgap 90%的预测最大范围
        ]

        list_L1_set = [
            "L1_SET_DATA SET_LEVELLER_INLET[mm]",
            "L1_SET_DATA SET_LEVELLER_OUTLET [mm]"
        ]

        #log.info("proc_trace... {}".format(len(global_var.gl_dq_last)))

        tm_now = datetime.now()
        model_param = {"type":1,"option1":2,"option2":1,"thickness":0.5} #模型宽展参数
        
        # 初始化时，没有数据，插入当前数据
        
        if len(global_var.gl_dq_last) == 0:
            log.info("init global deque plate data...")
            plate_data = {}
            plate_data["_id"] = tm_now.strftime("%Y-%m-%d %H:%M:%S")
            plate_data["plate_id"] = self.q[-1]["plate_id"]
            plate_data["material_id"] = self.q[-1]["material_id"]
            plate_data["steel_grade"] = self.q[-1]["steel grade"]
            plate_data["length"] = self.q[-1]["plate length"]
            plate_data["width"] = self.q[-1]["plate width"]
            plate_data["thickness"] = self.q[-1]["plate thickness"]
            plate_data["temp"] = self.q[-1]["plate act temperature"]

            set_val = self.model.calc_model(self.q[-1],model_param)
            if len(set_val):
                ###
                plate_data["set_inlet"] = set_val["Leverler inlet manual"]
                plate_data["set_outlet"] = set_val["Leverler outlet manual"]
                plate_data["set_flextion"]=set_val["Leverler flexion manual"]
            
            global_var.gl_dq_last.append(plate_data)


        if len(self.q)<2:
            log.info("self.q size < 2")
            return

        #calc 1, new plate
        if self.q[-1]["plate_id"]!= self.q[-2]["plate_id"]:
            #start calc1 set-value
            #todo
            #calc1
            #push data for gl_dq_last
            log.info("****** start calc1")
            plate_data = {}
            plate_data["_id"] = tm_now.strftime("%Y-%m-%d %H:%M:%S")
            plate_data["plate_id"] = self.q[-1]["plate_id"]
            plate_data["material_id"] = self.q[-1]["material_id"]
            plate_data["steel_grade"] = self.q[-1]["steel grade"]
            plate_data["length"] = self.q[-1]["plate length"]
            plate_data["width"] = self.q[-1]["plate width"]
            plate_data["thickness"] = self.q[-1]["plate thickness"]
            plate_data["temp"] = self.q[-1]["plate act temperature"]

            calc_plateid=self.q[-1]["plate_id"]
            set_val = self.model.calc_model(self.q[-1],model_param)
            
            
            #save plate data            
            if len(set_val):
                ###
                plate_data["set_inlet"] = set_val["Leverler inlet manual"]
                plate_data["set_outlet"] = set_val["Leverler outlet manual"]
                plate_data["set_flextion"]=set_val["Leverler flexion manual"]
                ###
                sav_values = {}
                sav_values["ctime"] = tm_now
                sav_values.update(self.q[-1].copy()) 
                sav_values_ext = {}
                for _it in list_result_name:
                    sav_values_ext["1st {}".format(_it)] = set_val[_it]
                
                sav_values.update(sav_values_ext)

                oids = self.db.insert(BASE_TABLE,sav_values)
                if len(oids):
                    self.synpd.insert(self.q[-1]["plate_id"], oids[0])
                
                #print(set_val)
            
            
            global_var.gl_dq_last.append(plate_data)

            return

        #calc 2
        if self.q[-1]["Forward mode"]:
            if (self.q[-1]["Before PinchRoll"] and self.q[-1]["Before Leveller"]) and self.q[-2]["Before Leveller"] == 0: 
                #cacl2 the set-value
                #todo
                LINE_STATUS["calc2_on"]=1
                LINE_STATUS["calc2_cd"]=5 #delay 5 times, include the start
                
            if self.q[-1]["Before PinchRoll"] and self.q[-1]["Before Leveller"]:

                if abs(self.q[-2][list_L1_set[0]]) <= 0.0001 and abs(self.q[-1][list_L1_set[0]]) > 0.0001:
                    log.info("L1 Set[{}] {} --> {}".format(list_L1_set[0],self.q[-2][list_L1_set[0]], self.q[-1][list_L1_set[0]]))
                    LINE_STATUS["L1_seton"] = 1

                if LINE_STATUS["calc2_on"] :
                    if LINE_STATUS["calc2_cd"]:
                        LINE_STATUS["calc2_cd"] -= 1
                    else:
                        LINE_STATUS["calc2_on"] = 0
                        log.info("****** start calc 2nd, tm {}".format(type(tm_now)))

                        pass_calc1 = False
                        plate_data={}
                        for _it in global_var.gl_dq_last:
                            if _it["plate_id"] == self.q[-1]["plate_id"]:
                                plate_data = _it
                                pass_calc1 = True
                                break

                        #log.info("##################### {}".format(tm_now.strftime("%Y-%m-%d %H:%M:%S")))
                        
                        #plate_date["_id"] = tm_now.strftime("%Y-%m-%d %H:%M:%S")
                        
                        plate_data["plate_id"] = self.q[-1]["plate_id"]
                        
                        plate_data["material_id"] = self.q[-1]["material_id"]
                        
                        plate_data["steel_grade"] = self.q[-1]["steel grade"]
                        
                        plate_data["length"] = self.q[-1]["plate length"]
                        
                        plate_data["width"] = self.q[-1]["plate width"]
                        
                        plate_data["thickness"] = self.q[-1]["plate thickness"]
                        plate_data["temp"] = self.q[-1]["plate act temperature"]
                       
                        
                        log.info("######## start 2nd calc & pre calc")
                        #start calc2 set-value
                        set_val = self.model.calc_model(self.q[-1],model_param)
                        #用户输入值反算矫直结果

                        model_param["type"] = 0
                        set_val_pre = self.model.calc_model(self.q[-1], model_param)

                        ######################################################
                        # @2020/01/21
                        # record model calc log

                        ######################################################
                        if len(set_val):
                            plate_data["set_inlet"] = set_val["Leverler inlet manual"]
                            plate_data["set_outlet"] = set_val["Leverler outlet manual"]
                            plate_data["set_flextion"]=set_val["Leverler flexion manual"]
                            sav_values = {}
                            
                            sav_values.update(self.q[-1].copy())
                            sav_values["recv_shape"] = 0 #是否完成板形数据的采集
                            sav_values_ext = {}
                            for _it in list_result_name:
                                sav_values_ext["2nd {}".format(_it)] = set_val[_it]
                            
                            if len(set_val_pre):
                                for _it in list_pre_result:
                                    sav_values_ext["pre {}".format(_it)] = set_val_pre[_it]
                               
                            sav_values.update(sav_values_ext)
                            
                            log.info("pre calc: {}".format(sav_values["pre flmg_mean"]))
                            oid = self.synpd.get_oid(self.q[-1]["plate_id"])
                            log.info("##### get oid: {}".format(oid))
                            if oid:
                                # log.info("update [{}], act temp: {}({}), save fieldnum: {}".format(self.q[-1]["plate_id"],self.q[-1]["plate act temperature"]
                                #                                                        ,sav_values_ext["plate act temperature"],len(sav_values_ext)))
                                # sav_values_ext["plate act temperature"] = self.q[-1]["plate act temperature"]
                                # self.db.update("flattensys",oid,sav_values_ext)
                                # sav_values_up = {}
                                # sav_values_up["ctime"] = datetime.now()
                                # sav_values_up.update(sav_values)
                                log.info("calc2 update data for {}[{}]".format(oid,sav_values["plate_id"]))
                                self.db.update(BASE_TABLE,oid,sav_values)
                                self.synpd.set_state(oid, "calc2")

                            else:
                                sav_value_ins = {}
                                sav_value_ins["ctime"]=tm_now
                                sav_value_ins.update(sav_values)
                                log.info("calc2 insert data for [{}]".format(sav_value_ins["plate_id"]))
                                oids = self.db.insert(BASE_TABLE,sav_value_ins)
                                
                                self.synpd.insert(self.q[-1]["plate_id"], oids[0],"calc2")
                            

                            if not pass_calc1:
                                global_var.gl_dq_last.append(plate_data)

                if LINE_STATUS["L1_seton"]:
                    
                    LINE_STATUS["L1_seton"] = 0
                    try:
                        oid = self.synpd.get_oid(self.q[-1]["plate_id"])
                        if oid:
                            sav_values = {}
                            for _it in list_L1_set:
                                sav_values[_it] = self.q[-1][_it]

                            self.db.update(BASE_TABLE,oid,sav_values)
                            log.info("update L1 set for {},[{:.2f},{:.2f}]".format(self.q[-1]["plate_id"]
                                                                                ,self.q[-1][list_L1_set[0]]
                                                                                ,self.q[-1][list_L1_set[1]]
                                                                                ))
                            #self.synpd.set_state(oid, "calc2")
                    except Exception as e:
                        log.error("save L1 set on err {}".format(e))

    #init the plc db block
    def init_plc_db(self):
        for data_item in DB_DATA:
            key = "DB_{}".format(data_item[0])
            edx = data_item[3]+data_item[4]

            if key in self.db_dict.keys():
                if self.db_dict[key].edx < edx:
                    self.db_dict[key].edx = edx
            else:
                blk = PLCDBlk(data_item[0],0,edx)
                self.db_dict[key] = blk

    #read plc db block
    def read_plc_db(self):
        for key in self.db_dict:
            data = self.client.db_read(self.db_dict[key].idx, self.db_dict[key].stx,self.db_dict[key].edx)
            self.db_dict[key].datablk = data
        
        ins_data = {}
        # ins_data_pre = {}
        for item in DB_DATA:
            
            key = "DB_{}".format(item[0])
            if item[2]=="float":
                _val, = struct.iter_unpack(">f",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = round(_val[0],4)
            elif item[2] == "double":
                _val, = struct.iter_unpack(">d",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = round(_val[0],4)
            elif item[2]=="int":
                _val, = struct.iter_unpack(">i",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = _val[0]
            elif item[2]=="uint":
                _val, = struct.iter_unpack(">I",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = _val[0]
            elif item[2]=="short" or item[2]=="word":#2bits
                _val, = struct.iter_unpack(">h",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = _val[0]
            elif item[2]=="char":
                _val, = struct.iter_unpack(">b",self.db_dict[key].datablk[item[4]:item[3]+item[4]])#DB_HEAD[item[0]][item[4]:item[3]+item[4]])
                val = _val[0]
            elif item[2]=="bool":
                _val, = struct.iter_unpack(">b",self.db_dict[key].datablk[item[4]:item[3]+item[4]])
                val = (_val[0]>>item[5])&0x01
                
            elif item[2]=="string":
                str1 = self.db_dict[key].datablk[item[4]:item[3]+item[4]]#DB_HEAD[item[0]][item[4]:item[3]+item[4]]
                val = str1.decode(encoding='utf-8')
                val = val.strip()

            ins_data[item[1]] = val
        
        return ins_data

    # def load_blk_info(self):
    #     for data_item in DB_DATA:
    #         key = "DB_{}".format(data_item[0])
    #         edx = data_item[3]+data_item[4]

    #         if key in self.db_dict.keys():
    #             if self.db_dict[key].edx < edx:
    #                 self.db_dict[key].edx = edx
    #         else:
    #             blk = PLCDBlk(data_item[0],0,edx)
    #             self.db_dict[key] = blk
            
def run_test():
    trace = CPlateTrace();
    trace.start()
    trace.join()

if __name__=="__main__":
    run_test()
